﻿using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;

using Ordinaire.Db;
using Ordinaire.Social.Data;

namespace Ordinaire.Social
{
    /// <summary>
    /// Represents a manager responsible for contact from SQL data source.
    /// </summary>
    public class ContactDataManager : JunctionDataManager<OContext>
    {
        #region Constructor

        public ContactDataManager()
        {
        }

        #endregion

        #region Public methods

        #region Addresses

        /// <summary>
        /// Retrieves the address data that matches with the specified details/parameters from the data source.
        /// </summary>
        /// <param name="unitNo">unit number</param>
        /// <param name="blockNo">block/street number</param>
        /// <param name="streetName">street name</param>
        /// <param name="buildingName">building name</param>
        /// <param name="suburb">suburb</param>
        /// <param name="state">state</param>
        /// <param name="postCode">postal code</param>
        /// <param name="country">country name</param>
        /// <returns>the relevant data; otherwise null</returns>
        public Addresses GetAddresses(string unitNo, string blockNo, string streetName, string buildingName, string suburb, string state, string postCode, string country)
        {
            return Context
                .Addresses
                .Where(Addresses.Matches(unitNo, blockNo, streetName, buildingName, suburb, state, postCode, country))
                .SingleOrDefault();
        }

        /// <summary>
        /// Stores values in Ordinaire.Social.Data.Addresses instance into SQL Server.
        /// </summary>
        /// <param name="data">instance of Ordinaire.Social.Data.Addresses</param>
        /// <returns>updated data with the generated unique id if insert is successful</returns>
        public Addresses Insert(Addresses data)
        {
            Addresses[] addresses = base.Insert<Addresses, int, string>(data);

            if (addresses == null || addresses.Length == 0)
            {
                return null;
            }
            return addresses[0];
        }

        /// <summary>
        /// Creates and stores new address details into the data source.
        /// </summary>
        /// <param name="unitNo">unit number</param>
        /// <param name="blockNo">block/street number</param>
        /// <param name="streetName">street name</param>
        /// <param name="buildingName">building name</param>
        /// <param name="suburb">suburb name</param>
        /// <param name="state">state name</param>
        /// <param name="postCode">postal code</param>
        /// <param name="country">country name</param>
        /// <returns>the address data that was created</returns>
        public Addresses InsertAddresses(string unitNo, string blockNo, string streetName, string buildingName, string suburb, string state, string postCode, string country)
        {
            // Sanitise text before saving
            unitNo       = unitNo.TrimExtraSpaces();
            blockNo      = blockNo.TrimExtraSpaces();
            streetName   = streetName.TrimExtraSpaces();
            buildingName = buildingName.TrimExtraSpaces();
            suburb       = suburb.TrimExtraSpaces();
            state        = state.TrimExtraSpaces();
            postCode     = postCode.TrimExtraSpaces();
            country      = country.TrimExtraSpaces();

            Addresses data = new Addresses();

            data.UnitNo       = unitNo;
            data.BlockNo      = blockNo;
            data.StreetName   = streetName;
            data.BuildingName = buildingName;
            data.Suburb       = suburb;
            data.State        = state;
            data.PostCode     = postCode;
            data.Country      = country;

            return Insert(data);
        }

        #endregion

        #region Organisations

        /// <summary>
        /// Returns the number of organisations in the data source with the specified name and optionally the provider name.
        /// </summary>
        /// <param name="name">the organisation name to look for</param>
        /// <param name="providerName">the provider name to look for</param>
        /// <returns>the number of organisations match with the specified criteria</returns>
        public int CountOrganisationsByName(string name, string providerName = null)
        {
            return Context
                .Organisations
                .Where(Organisations.NameIs(name, providerName))
                .Count();
        }

        /// <summary>
        /// Deletes data from OrganisationsAddresses with the specified organisation and addresses ids.
        /// </summary>
        /// <param name="organisationsId">the id of Organisations data to delete</param>
        /// <param name="addressesId">the id of Addresses data to delete</param>
        public void DeleteOrganisationsAddresses(int organisationsId, int addressesId)
        {
            OrganisationAddresses dataToDelete = base.GetData<OrganisationAddresses, Organisations, Addresses, int, string>(organisationsId, addressesId);
            Context.OrganisationsAddresses.DeleteOnSubmit(dataToDelete);
            Context.SubmitChanges();
        }

        /// <summary>
        /// Returns list of addresses associated with the specified Organisations data.
        /// </summary>
        /// <param name="data">the data from table "Organisations"</param>
        /// <returns>a collection of Addresses data associated with the specified Organisations data</returns>
        public Dictionary<string, Addresses> GetAddresses(Organisations data)
        {
            data.ThrowIfNull<ArgumentNullException>("data");
            return GetAddressesByOrganisationsId(data.Id);
        }

        /// <summary>
        /// Returns list of addresses associated with the specified Organisations id.
        /// </summary>
        /// <param name="id">the id of the data from table "Organisations"</param>
        /// <returns>a collection of Addresses data associated with the specified Organisations id</returns>
        public Dictionary<string, Addresses> GetAddressesByOrganisationsId(int id)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("Id '{0}'", id)));
            return base.GetDataBByIdAAsDictionary<OrganisationAddresses, Organisations, Addresses, int, string>(id);
        }

        /// <summary>
        /// Returns the data of an organisation with the specified id from the data source.
        /// </summary>
        /// <param name="id">the id of organisation data</param>
        /// <returns>instance of Ordinaire.Social.Data.Organisations if exists; otherwise null</returns>
        public Organisations GetOrganisations(int id)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("Id '{0}'", id)));
            return base.Get<Organisations, int, string>(id);
        }

        /// <summary>
        /// Retrieves the data of an organisation with the specified name from the data source.
        /// </summary>
        /// <param name="name">organisation name to retrieve</param>
        /// <param name="providerName">the provider name</param>
        /// <returns>instance of Ordinaire.Social.Data.Organisations if exists; otherwise null</returns>
        public Organisations GetOrganisations(string name, string providerName = null)
        {
            Organisations data = Context
                .Organisations
                .Where(Organisations.NameIs(name, providerName))
                .FirstOrDefault();

            return data;
        }

        /// <summary>
        /// Creates and stores new organisation details into the data source.
        /// </summary>
        /// <param name="name">the organisation name to create</param>
        /// <param name="defaultAddressesId">id of the default address</param>
        /// <param name="providerName">the provider name</param>
        /// <param name="providerAssemblyName">the provider assembly name</param>
        /// <returns>the organisation data that was created</returns>
        public Organisations InsertOrganisations(string name, int defaultAddressesId = 0, string providerName = null, string providerAssemblyName = null)
        {
            name.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "name"));

            Organisations organisations        = new Organisations();
            organisations.Name                 = name;
            organisations.DefaultAddressesId   = defaultAddressesId;
            organisations.ProviderName         = providerName;
            organisations.ProviderAssemblyName = providerAssemblyName;

            return Insert(organisations);
        }

        /// <summary>
        /// Stores values in Ordinaire.Social.Data.Organisations instance into SQL server.
        /// </summary>
        /// <param name="data">instance of Ordinaire.Social.Data.Organisations</param>
        /// <returns>updated data with the generated unique id if insert is successful</returns>
        public Organisations Insert(Organisations data)
        {
            Organisations[] organisations = base.Insert<Organisations, int, string>(data);

            if (organisations == null || organisations.Length == 0)
            {
                return null;
            }
            return organisations[0];
        }

        /// <summary>
        /// Stores values in Ordinaire.Social.Data.OrganisationsAddresses instance into SQL Server.
        /// </summary>
        /// <param name="data">instance of Ordinaire.Social.Data.OrganisationsAddresses</param>
        /// <returns>updated data with the generated unique id if insert is successful</returns>
        public OrganisationAddresses Insert(OrganisationAddresses data)
        {
            OrganisationAddresses[] returnData = base.Insert<OrganisationAddresses, int, string>(data);

            if (returnData == null || returnData.Length == 0)
            {
                return null;
            }
            return returnData[0];
        }

        /// <summary>
        /// Updates data in table "Organisations" with the new name.
        /// </summary>
        /// <param name="id">the id of the person contact data</param>
        /// <param name="name">organisation's name</param>
        /// <returns>the modified data</returns>
        public Organisations UpdateOrganisations(int id, string name)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(
                ExceptionMessage.Invalid,
                String.Format("Id '{0}'", id)
            ));

            Organisations currentData = base.Get<Organisations, int, string>(id);
            if (currentData == null)
            {
                throw new ArgumentException(String.Format(
                    ExceptionMessage.NotExistsIn,
                    String.Format("Data with id '{0}'", id),
                    String.Format("table '{0}'", currentData.GetType().AttributeName<TableAttribute>())
                ));
            }

            currentData.Name = name;
            Context.SubmitChanges();
            return currentData;
        }

        #endregion

        #region People

        /// <summary>
        /// Returns the total number of people in the source with the specified firstname, middlename, lastname, and dateOfBirth, and optionally the provider name.
        /// </summary>
        /// <param name="firstName">person's first name</param>
        /// <param name="middleName">person's middle name</param>
        /// <param name="lastName">person's last name</param>
        /// <param name="dateOfBirth">his/her date of birth</param>
        /// <param name="providerName">the provider name</param>
        /// <returns></returns>
        public int CountPeople(string firstName, string middleName, string lastName, DateTime? dateOfBirth = null, string providerName = null)
        {
            return Context
                .People
                .Where(People.AreIdentical(firstName, middleName, lastName, dateOfBirth, providerName))
                .Count();
        }

        /// <summary>
        /// Deletes data from PeopleAddresses with the specified people and addresses ids.
        /// </summary>
        /// <param name="peopleId">the id of People data to delete</param>
        /// <param name="addressId">the id of Addresses data to delete</param>
        public void DeletePeopleAddresses(int peopleId, int addressId)
        {
            PersonAddresses dataToDelete = base.GetData<PersonAddresses, People, Addresses, int, string>(peopleId, addressId);
            if (dataToDelete == null)
            {
                ExceptionFactory.Create<ArgumentException>(ExceptionMessage.NotExists, String.Format("data with peopleId '{0}' and addressId '{1}'", peopleId, addressId));
            }

            Context.PeopleAddresses.DeleteOnSubmit(dataToDelete);
            Context.SubmitChanges();
        }

        /// <summary>
        /// Returns a value indicating whether there is a person whose name as the one specified.
        /// </summary>
        /// <param name="name">the name to check against</param>
        /// <returns>true if the same name already exists; otherwise false</returns>
        public bool Exists(string name)
        {
            return base.ExistsByName<People, int, string>(name);
        }

        /// <summary>
        /// Returns list of addresses associated with the specified People data.
        /// </summary>
        /// <param name="data">the data from table "People"</param>
        /// <returns>a collection of Addresses data associated with the specified People data</returns>
        public Dictionary<string, Addresses> GetAddresses(People data)
        {
            data.ThrowIfNull<ArgumentNullException>("data");
            return GetAddressesByPeopleId(data.Id);
        }

        /// <summary>
        /// Returns list of addresses associated with the specified People id.
        /// </summary>
        /// <param name="data">the id of data from table "People"</param>
        /// <returns>a collection of Addresses data associated with the specified People id</returns>
        public Dictionary<string, Addresses> GetAddressesByPeopleId(int id)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("Id '{0}'", id)));
            return base.GetDataBByIdAAsDictionary<PersonAddresses, People, Addresses, int, string>(id);
        }

        /// <summary>
        /// Retrieves the data with the specified id from the data source.
        /// </summary>
        /// <param name="id">the row id</param>
        /// <returns>instance of Ordinaire.Social.Data.People if exists; otherwise null</returns>
        public People GetPeople(int id)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("Id '{0}'", id)));
            return base.Get<People, int, string>(id);
        }

        /// <summary>
        /// Retrieves the data that matches with the specified details/parameters from the data source.
        /// </summary>
        /// <param name="firstName">person's first name</param>
        /// <param name="middleName">person's middle name</param>
        /// <param name="lastName">person's last name</param>
        /// <param name="dateOfBirth">person's birth date</param>
        /// <returns>instance of Ordinaire.Social.Data.People if exists; otherwise null</returns>
        public People GetPeople(string firstName, string middleName, string lastName, DateTime? dateOfBirth = null)
        {
            return GetPeople(firstName, middleName, lastName, null, dateOfBirth);
        }

        /// <summary>
        /// Retrieves the data that matches with the specified details/parameters from the data source.
        /// </summary>
        /// <param name="firstName">person's first name</param>
        /// <param name="middleName">person's middle name</param>
        /// <param name="lastName">person's last name</param>
        /// <param name="providerName">the provider fullname</param>
        /// <param name="dateOfBirth">person's birth date</param>
        /// <returns>instance of Ordinaire.Social.Data.People if exists; otherwise null</returns>
        public People GetPeople(string firstName, string middleName, string lastName, string providerName, DateTime? dateOfBirth = null)
        {
            People data = Context
                .People
                .Where(People.AreIdentical(firstName, middleName, lastName, dateOfBirth, providerName))
                .FirstOrDefault();

            return data;
        }

        /// <summary>
        /// Creates and stores a person details into the data source.
        /// </summary>
        /// <param name="firstName">person's first name</param>
        /// <param name="middleName">person's middle name</param>
        /// <param name="lastName">person's last name</param>
        /// <param name="dateOfBirth">person's date of birth</param>
        /// <param name="defaultAddressesId">the id of person's default address</param>
        /// <param name="providerName">the provider name</param>
        /// <param name="providerAssemblyName">the provider assembly name</param>
        /// <returns>the person data that was created</returns>
        public People InsertPeople(string firstName, string middleName, string lastName, DateTime? dateOfBirth = null, int defaultAddressesId = 0, string providerName = null, string providerAssemblyName = null)
        {
            firstName.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "firstName"));

            People data  = new People {
                FirstName            = firstName,
                MiddleName           = middleName,
                LastName             = lastName,
                DateOfBirth          = dateOfBirth,
                DefaultAddressesId   = defaultAddressesId,
                ProviderName         = providerName,
                ProviderAssemblyName = providerAssemblyName
            };

            return Insert(data);
        }

        /// <summary>
        /// Stores values in Ordinaire.Social.Data.People instance into SQL Server.
        /// </summary>
        /// <param name="data">instance of Ordinaire.Social.Data.People</param>
        /// <returns>updated data with the generated unique id if insert is successful</returns>
        public People Insert(People data)
        {
            People[] people = base.Insert<People, int, string>(data);

            if (people == null || people.Length == 0)
            {
                return null;
            }
            return people[0];
        }

        /// <summary>
        /// Stores values in Ordinaire.Social.Data.PeopleAddresses instance into SQL Server.
        /// </summary>
        /// <param name="data">instance of Ordinaire.Social.Data.PeopleAddresses</param>
        /// <returns>updated data with the generated unique id if insert is successful</returns>
        public PersonAddresses Insert(PersonAddresses data)
        {
            PersonAddresses[] returnData = base.Insert<PersonAddresses, int, string>(data);
            
            if (returnData == null || returnData.Length == 0)
            {
                return null;
            }
            return returnData[0];
        }

        /// <summary>
        /// Updates data in table "People" with the new first name, middle name, last name, date of birth, default address id, and provider name.
        /// </summary>
        /// <param name="id">the id of the person contact data</param>
        /// <param name="firstName">person's first name</param>
        /// <param name="middleName">person's middle name</param>
        /// <param name="lastName">person's last name</param>
        /// <param name="dateOfBirth">his/her date of birth</param>
        /// <param name="defaultAddressesId">the id of person's default address</param>
        /// <param name="providerName">the provider name</param>
        /// <returns>the modified data</returns>
        public People UpdatePeople(int id, string firstName, string middleName, string lastName, DateTime? dateOfBirth = null, int defaultAddressId = 0, string providerName = null)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(
                ExceptionMessage.Invalid,
                String.Format("Id '{0}'", id)
            ));

            People currentData = base.Get<People, int, string>(id);

            if (currentData == null)
            {
                throw new ArgumentException(String.Format(
                    ExceptionMessage.NotExistsIn,
                    String.Format("Data with id '{0}'", id),
                    String.Format("table '{0}'", currentData.GetType().AttributeName<TableAttribute>())
                ));
            }

            currentData.FirstName  = firstName;
            currentData.MiddleName = middleName;
            currentData.LastName   = lastName;

            if (dateOfBirth.HasValue)
            {
                currentData.DateOfBirth = dateOfBirth;
            }

            if (defaultAddressId > 0)
            {
                currentData.DefaultAddressesId = defaultAddressId;
            }

            if (!String.IsNullOrEmpty(providerName)) {
                currentData.ProviderName = providerName;
            }

            Context.SubmitChanges();
            return currentData;
        }

        #endregion

        #endregion
    }
}
